package com.stackexchange.webapis.methodgroups
{
	import com.adobe.serialization.json.JSON;
	import com.adobe.serialization.json.JSONDecoder;
	import com.stackexchange.webapis.Answer;
	import com.stackexchange.webapis.AnswerList;
	import com.stackexchange.webapis.Badge;
	import com.stackexchange.webapis.BadgeCount;
	import com.stackexchange.webapis.BadgeList;
	import com.stackexchange.webapis.Comment;
	import com.stackexchange.webapis.CommentList;
	import com.stackexchange.webapis.Migration;
	import com.stackexchange.webapis.Question;
	import com.stackexchange.webapis.QuestionList;
	import com.stackexchange.webapis.StackExchangeError;
	import com.stackexchange.webapis.Style;
	import com.stackexchange.webapis.Tag;
	import com.stackexchange.webapis.User;
	import com.stackexchange.webapis.UserList;
	import com.stackexchange.webapis.enums.PostType;
	import com.stackexchange.webapis.enums.RankType;
	import com.stackexchange.webapis.enums.StateType;
	import com.stackexchange.webapis.enums.UserType;

	/**
	 * Parse JSON object returned by the server after a method call.
	 */
	public class ResponseParser
	{
		/**
		 * Parse a JSON and returns the parsed result to the method group caller
		 * 
		 * @param rawData The JSON string response we got from the loader call
		 * @param parseFunction The function to parse the response with 
		 * @param propertyName The property in event.data that the results should be placed
		 */
		public function process(rawData:String,
								parseFunction:Function,
								propertyName:String):Object
		{
			
			
			var res:Object = processResponse(rawData, parseFunction, propertyName);
			return res;
		}
		
		private function processResponse(rawData:String, parseFunction:Function, propertyName:String):Object
		{
			var result:Object = new Object();
			result.data = new Object();
			
			var jsonResult:Object = JSON.decode(rawData, true);
			
			if (jsonResult.error)
				return processError(result, jsonResult);
			else
				return processResult(result, parseFunction, propertyName, jsonResult);
		}
		
		private function processError(result:Object, jsonResult:Object):Object
		{
			result.success = false;
			
			var error:StackExchangeError = parseError(jsonResult);
			result.data.error = error;
			
			return result;
		}
		
		private function processResult(result:Object, parseFunction:Function, propertyName:String, jsonResult:Object):Object
		{
			result.data[propertyName] = parseFunction(jsonResult);
			result.success = true;

			return result;
		}
		
		/**
		 * Converts an answers result JSON object into an AnswerList instance
		 */
		public function parseAnswers(jsonResult:Object):AnswerList
		{
			var answerList:AnswerList = new AnswerList();
			answerList.page = jsonResult.page;
			answerList.pageSize = jsonResult.pagesize;
			answerList.total = jsonResult.total;
			
			for each(var jsonAnswer:Object in jsonResult.answers)
			{
				answerList.addAnswer(parseAnswer(jsonAnswer));
			}
			
			return answerList;
		}
		
		public function parseAnswer(jsonAnswer:Object):Answer
		{
			if (jsonAnswer)
			{
				var answer:Answer = new Answer();
				answer.accepted = jsonAnswer.accepted;
				answer.answerCommentsUrl = jsonAnswer.answer_comments_url;
				answer.answerId = jsonAnswer.answer_id;
				answer.body = jsonAnswer.body;
				answer.communityOwned = jsonAnswer.community_owned;
				answer.creationDate = parseDate(jsonAnswer.creation_date);
				answer.downVoteCount = jsonAnswer.down_vote_count;
				answer.lastActivityDate = parseDate(jsonAnswer.last_activity_date);
				answer.lastEditDate = parseDate(jsonAnswer.last_edit_date);
				answer.lockedDate = parseDate(jsonAnswer.locked_date);
				answer.owner = parseUser(jsonAnswer.owner);
				answer.questionId = jsonAnswer.question_id;
				answer.score = jsonAnswer.score;
				answer.title = jsonAnswer.title;
				answer.upVoteCount = jsonAnswer.up_vote_count;
				answer.viewCount = jsonAnswer.view_count;
				
				for each(var jsonComment:Object in jsonAnswer.comments)
				{
					answer.addComment(parseComment(jsonComment));
				}
				return answer;
			}
			
			return null;
		}
		
		/**
		 * Converts a users result JSON object into a UserList instance
		 **/
		public function parseUsers(jsonUsers:Object):UserList
		{
			var userList:UserList = new UserList();
			userList.page = jsonUsers.page;
			userList.pageSize = jsonUsers.pagesize;
			userList.total = jsonUsers.total;
			
			for each(var jsonUser:Object in jsonUsers.users)
			{
				userList.addUser(parseUser(jsonUser));
			}
			
			return userList;
			
		}
		/**
		 * Converts an user result JSON object into an User instance
		 */
		public function parseUser(jsonUser:Object):User
		{
			var user:User;
			
			if (jsonUser)
			{
				user = new User();
				user.displayName = jsonUser.display_name;
				user.emailHash = jsonUser.email_hash;
				user.reputation = jsonUser.reputation;
				user.userId = jsonUser.user_id;
				user.userType = parseUserType(jsonUser.user_type);
				user.creationDate = parseDate(jsonUser.creation_date);
				user.age = jsonUser.age;
				user.lastAccessDate = parseDate(jsonUser.last_access_date);
				user.websiteUrl = jsonUser.website_url;
				user.location = jsonUser.location;
				user.aboutMe = jsonUser.about_me;
				user.questionCount = jsonUser.question_count;
				user.answerCount = jsonUser.answer_count;
				user.viewCount = jsonUser.view_count;
				user.upVoteCount = jsonUser.up_vote_count;
				user.downVoteCount = jsonUser.down_vote_count;
				user.acceptRate = jsonUser.accept_rate;
				user.associationId = jsonUser.association_id;
				user.userQuestionsUrl = jsonUser.user_questions_url;
				user.userAnswersUrl = jsonUser.user_answers_url;
				user.userFavoritesUrl = jsonUser.user_favorites_url;
				user.userTagsUrl = jsonUser.user_tags_url;
				user.userBadgesUrl = jsonUser.user_badges_url;
				user.userTimelineUrl = jsonUser.user_timeline_url;
				user.userMentionedUrl = jsonUser.user_mentioned_url;
				user.userCommentsUrl = jsonUser.user_comments_url;
				user.userReputationUrl = jsonUser.user_reputation_url;
				user.badgeCounts = parseBadgeCount(jsonUser.badge_counts);
				user.timedPenaltyDate = parseDate(jsonUser.timed_penalty_date);
			}
			
			return user; 
		}
		
		/**
		 * Converts a user type string into an UserType instance
		 */
		public function parseUserType(jsonUserType:String):UserType
		{
			switch (jsonUserType)
			{
				case "anonymous": return UserType.ANONYMOUS; break;
				case "moderator": return UserType.MODERATOR; break;
				case "registered": return UserType.REGISTERED; break;
				case "unregistered": return UserType.UNREGISTERED; break;
				default : return null;
			}
			
			return null;
		}
		
		/**
		 * Converts a comment result JSON object into an CommentList instance
		 */
		public function parseComments(jsonResult:Object):CommentList
		{
			var commentList:CommentList = new CommentList();
			commentList.page = jsonResult.page;
			commentList.pageSize = jsonResult.pagesize;
			commentList.total = jsonResult.total;
			
			for each(var jsonAnswer:Object in jsonResult.comments)
			{
				var comment:Comment = parseComment(jsonAnswer);;
				commentList.addComment(comment);
			}
			
			return commentList;
		}
		
		/**
		 * Converts a comment result JSON object into a Comment instance
		 */
		public function parseComment(jsonComment:Object):Comment
		{
			var comment:Comment;
			
			if (jsonComment)
			{
				comment = new Comment();
				comment.commentId = jsonComment.comment_id;
				comment.creationDate = parseDate(jsonComment.creation_date);
				comment.owner = parseUser(jsonComment.owner);
				comment.replyToUser = parseUser(jsonComment.reply_to_user);
				comment.postId = jsonComment.post_id;
				comment.postType = parsePostType(jsonComment.post_type);
				comment.score = jsonComment.score;
				comment.body = jsonComment.body;
			}
			
			return comment;
		}
		
		public function parsePostType(jsonPostType:Object):PostType
		{
			switch (jsonPostType)
			{
				case "question": return PostType.QUESTION; break;
				case "answer": return PostType.ANSWER; break;
				default : return null;
			}
			
			return null;
		}
		
		/**
		 * Converts a badges result JSON object into an BadgeList instance
		 */
		public function parseBadges(jsonResult:Object):BadgeList
		{
			var badgeList:BadgeList = new BadgeList();
			badgeList.page = jsonResult.page;
			badgeList.pageSize = jsonResult.pagesize;
			badgeList.total = jsonResult.total;
			
			for each(var jsonAnswer:Object in jsonResult.badges)
			{
				var badge:Badge = parseBadge(jsonAnswer);
				badgeList.addBadge(badge);
			}
			
			return badgeList;
		}
		
		/**
		 * Converts a badge result JSON object into a Badge instance
		 */
		public function parseBadge(jsonBadge:Object):Badge
		{
			var badge:Badge;
			
			if (jsonBadge)
			{
				badge = new Badge();
				badge.badgeId = jsonBadge.badge_id;
				badge.rank = parseRankType(jsonBadge.rank);
				badge.name = jsonBadge.name;
				badge.description = jsonBadge.description;
				badge.awardCount = jsonBadge.award_count;
				badge.tagBased = jsonBadge.tag_based;
				badge.user = parseUser(jsonBadge.user);
				badge.badgesRecipientsUrl = jsonBadge.badges_recipients_url;
			}
			
			return badge;
		}
		
		/**
		 * Converts a badge counts result JSON object into a BadgeCount instance
		 **/
		public function parseBadgeCount(jsonBadgeCounts:Object):BadgeCount
		{
			if (jsonBadgeCounts)
			{
				var badgeCount:BadgeCount = new BadgeCount;
				badgeCount.bronze = jsonBadgeCounts.bronze;
				badgeCount.silver = jsonBadgeCounts.silver;
				badgeCount.gold = jsonBadgeCounts.gold;
				
				return badgeCount;
			}
			else
			{
				return null;
			}
					
			
		}
		
		/**
		 * Converts a rank result JSON object into a RankType instance
		 */
		public function parseRankType(jsonRankType:Object):RankType
		{
			switch (jsonRankType)
			{
				case "bronze": return RankType.BRONZE; break;
				case "silver": return RankType.SILVER; break;
				case "gold": return RankType.GOLD; break;
				default : return null;
			}
			
			return null;
		}
		
		/**
		 * Convert a json error to a StackExchangeError instance
		 **/
		public function parseError(jsonError:Object):StackExchangeError
		{
			if (jsonError)
			{
				var error:StackExchangeError = new StackExchangeError();
				error.message = jsonError.error.message;
				error.code = jsonError.error.code;
				return error;
			}
			
			return null;
		}
		
		/**
		 * Converts a questions result JSON object into a QuestionList instance
		 **/
		public function parseQuestions(jsonQuestions:Object):QuestionList
		{
			var questionList:QuestionList = new QuestionList();
			questionList.page = jsonQuestions.page;
			questionList.pageSize = jsonQuestions.pagesize;
			questionList.total = jsonQuestions.total;
			
			for each(var jsonQuestion:Object in jsonQuestions.questions)
			{
				questionList.addQuestion(parseQuestion(jsonQuestion));
			}
			
			return questionList;
			
		}
		
		/**
		 * Convert a json question to a Question instance
		 **/
		public function parseQuestion(jsonQuestion:Object):Question
		{
			if (jsonQuestion)
			{
				var question:Question = new Question();
				question.acceptedAnswerId = jsonQuestion.accepted_answer_id;
				question.answerCount = jsonQuestion.answer_count;
				for each(var jsonAnswer:Object in jsonQuestion.answers)
				{
					question.addAnswer(parseAnswer(jsonAnswer));
				}
				question.body = jsonQuestion.body;
				question.bountyAmount = jsonQuestion.bounty_amount;
				question.bountyClosesDate = parseDate(jsonQuestion.bounty_closes_date);
				question.closedDate = parseDate(jsonQuestion.closed_date);
				question.closedReason = jsonQuestion.closed_reason;
				for each(var jsonComment:Object in jsonQuestion.comments)
				{
					question.addComment(parseComment(jsonComment));
				}
				question.communityOwned = jsonQuestion.community_owned;
				question.creationDate = parseDate(jsonQuestion.creation_date);
				question.downVoteCount = jsonQuestion.down_vote_count;
				question.favoriteCount = jsonQuestion.favorite_count;
				question.lastActivityDate = parseDate(jsonQuestion.last_activity_date);
				question.lockedDate = parseDate(jsonQuestion.locked_date);
				question.migrated = parseMigration(jsonQuestion.migrated);
				question.owner = parseUser(jsonQuestion.owner);
				question.protectedDate = parseDate(jsonQuestion.protected_date);
				question.questionAnswersUrl = jsonQuestion.question_answers_url;
				question.questionCommentsUrl = jsonQuestion.question_comments_url;
				question.questionId = jsonQuestion.question_id;
				question.questionTimelineUrl = jsonQuestion.question_timeline_url;
				question.score = jsonQuestion.score;
				for each(var jsonTag:Object in jsonQuestion.tags)
				{
					question.addTag(parseTag(jsonTag));
				}
				question.title = jsonQuestion.title;
				question.upVoteCount = jsonQuestion.up_vote_count;
				question.viewCount = jsonQuestion.view_count;
				
				return question;
			}
			
			return null;
		}
		
		/**
		 * Convert a migrated json object into a Migration instance
		 */
		public function parseMigration(jsonMigration:Object):Migration
		{
			var migration:Migration;
			
			if (jsonMigration)
			{
				migration = new Migration();
				for each(var jsonAlias:Object in migration.aliases)
				{
					migration.aliases.push(jsonAlias as String);
				}
				migration.apiEndpoint = jsonMigration.api_endpoint;
				migration.description = jsonMigration.description;
				migration.iconUrl = jsonMigration.icon_url;
				migration.logoUrl = jsonMigration.logo_url;
				migration.newQuestionId = jsonMigration.new_question_id;
				migration.onDate = parseDate(jsonMigration.on_date);
				migration.siteUrl = jsonMigration.site_url;
				migration.state = parseStateType(jsonMigration.state);
				migration.styling = parseStyle(jsonMigration.styling);
				migration.toSite = jsonMigration.to_site;
			}
			
			return migration;
		}
		
		/**
		 * Converts a state type string into an StateType instance
		 */
		public function parseStateType(jsonStateType:String):StateType
		{
			switch (jsonStateType)
			{
				case "normal": return StateType.NORMAL; break;
				case "closed_beta": return StateType.CLOSED_BETA; break;
				case "open_beta": return StateType.OPEN_BETA; break;
				case "linked_meta": return StateType.LINKED_META; break;
				default : return null;
			}
			
			return null;
		}
		
		/**
		 * Convert a styling json object into a Style instance
		 */
		public function parseStyle(jsonStyle:Object):Style
		{
			var style:Style;
			
			if (jsonStyle)
			{
				style = new Style();
			}
			
			return style;
		}
		
		/**
		 * Convert a json tag object into a Tag instance
		 */
		public function parseTag(jsonTag:Object):Tag
		{
			var tag:Tag;
			
			if (jsonTag)
			{
				tag = new Tag();
				if (jsonTag is String)
				{
					tag.name = jsonTag as String;
				}	
				else
				{
					tag.count = jsonTag.count;
					tag.fulfillsRequired = jsonTag.fullfills_required;
					tag.name = jsonTag.name;
					tag.restrictedTo = parseUserType(jsonTag.restricted_to);
					tag.userId = jsonTag.user_id;
				}
			}
			
			return tag;
		}
		
		/**
		 * Convert an epoch UNIX date into a Date instance
		 */
		public function parseDate(epoch:Number):Date
		{
			if (isNaN(epoch))
				return null;
			
			var date:Date = new Date();
			date.setTime(epoch*1000);
			return date;
		}
	}
}